#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author          : ricsy
@File            : jinja_filters.py
@Create Time     : 2025/6/3 00:49
@Last Modified   :
@Description     :
"""
import os
import re
from datetime import datetime
from gettext import translation

from bs4 import BeautifulSoup

from sphinx.application import Sphinx
from src.settings import logger
from utils.config_processor import ConfigProcessor


config = ConfigProcessor()

# 加载配置信息
default_ns = config.as_namespace("default")
html_ns = config.as_namespace("html")
plugins_ns = config.as_namespace("plugins")

gettext_domain = default_ns.gettext_domain
locale_dirs = default_ns.locale_dirs
language = default_ns.language
html_theme = html_ns.html_theme
html_last_updated_fmt = plugins_ns.config["html_last_updated_fmt"]


class JinjaFilters:
    """
    自定义 Jinja 过滤器
    """

    # 预编译正则表达式，避免每次调用都重新编译，这样能提升速度
    _TITLE_PATTERN = re.compile(r"^(\d+\.)+$")
    _TITLE_NUMERIC_PATTERN = re.compile(r"^\s*(\d+\.)+\d*\s*|\d+[\u4e00-\u9fa5]")
    # 配置化缩进字符（全角空格）
    _INDENT_CHAR = "　"

    def __init__(self, app: Sphinx):
        self.app = app
        self.env = self.app.builder.templates.environment

    def add_filters(self):
        """
        添加自定义过滤器
        :return:
        """
        logger.info("[sphinx-extend] ⚡ 加载过滤器", 1)
        self.env.filters["count_headers"] = self._count_headers
        self.env.filters["add_h1_hidden_class"] = self._add_h1_hidden_class_filter
        self.env.filters["translate_filter"] = self.translate_filter
        self.env.filters["humanize_time_fileter"] = self.humanize_time_fileter
        self.env.filters["datetime_filter"] = self.datetime_filter
        self.env.filters["author_filter"] = self.author_filter

    def _count_headers(self, toc_html):
        """
        统计目录标题数量
        :param toc_html: 目录 HTML 内容
        :return: 满足包含序号的目录标题数量
        """
        soup = BeautifulSoup(toc_html, "html.parser")
        links = soup.find_all("a")
        # 情况1：完全没有链接
        if not links:
            return 0

        # 情况2：检查所有链接文本是否都不含序号
        has_numbered_text = any(
            self._TITLE_NUMERIC_PATTERN.search(link.text.strip()) for link in links
        )
        return len(links) if has_numbered_text else 0

    @staticmethod
    def add_element_class(element, class_name: list[str] | str):
        # 获取现有的 class 属性值，若不存在则初始化为空列表
        existing_classes = element.get("class", None)
        if existing_classes:
            existing_classes = []

        # 添加新的类名
        if isinstance(class_name, str):
            class_name = [class_name]
        existing_classes += class_name
        return existing_classes

    def _add_h1_hidden_class_filter(self, toc_html):
        """
        隐藏目录中的 H1 标题链接
        :param toc_html: 目录 HTML 内容
        :return: 更新后的目录 HTML 内容
        """
        soup = BeautifulSoup(toc_html, "html.parser")
        for link in soup.select('a[href^="#"]'):
            # 链接文本
            link_text = link.text.strip()
            # 链接 class
            link["class"] = self.add_element_class(
                link, ["reference", "internal", "no-tippy", "text-truncate"]
            )
            # 链接序号
            link_id = link_text.split(" ")[0]
            # 标题界别
            title_level = self._get_title_level(link_id)
            #  H1 标题添加 h1-link-hidden 类
            if title_level == 1:
                link["class"] = self.add_element_class(link, "h1-link-hidden")
            # H3 及以下标题添加缩进
            elif title_level > 2:
                indented_text = self._INDENT_CHAR * (title_level - 2) + link_text
                link.string = indented_text
        return str(soup)

    def _get_title_level(self, link_id, max_level=6):
        """
        标题级别
        :param link_id: 链接序号
        :param max_level: 最大标题级别，默认为 6
        :return: 标题级别，0 表示未识别，其他为有效级别
        """
        # 格式验证
        if not self._TITLE_PATTERN.fullmatch(link_id):
            return 0

        # 计算点号数量
        dot_count = link_id.count(".")
        return dot_count if 1 <= dot_count <= max_level else 0

    @staticmethod
    def humanize_time_raw(date_str, fmt="%Y-%m-%d"):
        """
        将时间戳转换为人类可读的时间
        :param date_str: 标准时间字符串
        :param fmt: 时间格式
        :return: (翻译键, 参数字典)
        """
        try:
            last_updated = datetime.strptime(date_str, fmt)
            now = datetime.now()
            delta = now - last_updated
            days = delta.days

            if days == 0:
                return "Updated today", {}
            elif days < 7:
                return "Updated %(days)d days ago", {"days": days}
            elif days < 30:
                return "Updated %(weeks)d weeks ago", {"weeks": days // 7}
            elif days < 365:
                return "Updated %(months)d months ago", {"months": days // 30}
            else:
                years = days // 365
                return (
                    ("Updated %(years)d years ago", {"years": years})
                    if years > 1
                    else ("Updated last year", {})
                )
        except (ValueError, TypeError):
            return "No update time recorded", {}

    def humanize_time_fileter(self, date_str, fmt="%Y-%m-%d"):
        """集成翻译的时间格式化"""
        time_data = self.humanize_time_raw(date_str, fmt)
        return self.translate_filter(time_data[0], **time_data[1])

    @staticmethod
    def datetime_filter(date_str, date_fmt, to_fmt="%Y-%m-%d"):
        """将字符串转为时间对象并格式化"""
        try:
            # 支持中文格式输入
            date_obj = datetime.strptime(date_str, date_fmt)
            return date_obj.strftime(to_fmt)
        except (ValueError, AttributeError):
            return date_str  # 失败时原样返回

    def translate_filter(self, msg_id, **kwargs):
        """
        通用翻译过滤器：TODO 当前仅支持从一个目录下加载翻译文件
        ：param msg_id: 要翻译的消息 ID
        ：param kwargs: 替换占位符的键值对
        用法: {{ "Hello %(name)s"|translate(name="World") }}
        """
        try:
            # 必须为绝对路径
            locale_dir = str(os.path.join(self.app.srcdir, locale_dirs[0]))
            languages = [language]
            t = translation(gettext_domain, locale_dir, languages, fallback=True)
            return t.gettext(msg_id) % kwargs
        except Exception:
            return msg_id % kwargs  # 翻译失败时回退原始文本

    @staticmethod
    def set_default_author(author):
        """
        设置默认作者
        :param author: 默认作者
        :return:
        """
        return author if author else "匿名"

    @staticmethod
    def parse_authors(author_str):
        """
        解析多种分隔符的作者字符串
        :param author_str: 包含分隔符的作者字符串
        :return: 去重后的作者列表
        """
        if not author_str:
            return []

        # 统一替换所有分隔符为逗号
        normalized = author_str.replace("|", ",").replace("、", ",")
        seen = set()
        authors = []

        for part in normalized.split(","):
            author = part.strip()
            # 去重且保留首次出现顺序
            if author and author not in seen:
                seen.add(author)
                authors.append(author)

        return authors  # 保持顺序一致性

    def author_filter(self, meta, default_authors=None):
        """
        作者过滤器
        :param meta: 页面元数据
        :param default_authors: 默认作者
        :return: 规范化后的作者显示字符串
        """
        # 处理默认值
        default = self.set_default_author(default_authors)

        # 处理空元数据情况
        if not meta:
            return default
        # 获取作者信息
        author_value = None
        try:
            if isinstance(meta, dict):
                author_value = meta.get("author")
            elif hasattr(meta, "author"):
                author_value = getattr(meta, "author")
        except Exception as e:
            print(f"获取作者信息出错: {str(e)}")
            return default

        # 处理作者字符串
        if not author_value:
            return default

        authors = self.parse_authors(str(author_value))
        return " | ".join(authors) if authors else default
